/*
 * XMLStream.java
 *
 * Copyright 2011 Anish
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 *     
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.aosp.nanoxstream;

import java.util.Enumeration;
import java.util.Vector;

import nanoxmlj2me.kXMLElement;


/**
 * <code>XMLStream.java</code>
 * <p> </p>
 * @author Anish , anishkumar.ka@gmail.com
 * <dt><b>Date: </b><dd>Jan 5, 2011
 * <dt><b>Module: </b><dd>nanoxstream
 * <dt><b>Package: </b><dd>com.aosp.nanoxstream
 * <dt>TODO:<dd>
 */
public class XMLStream {
	
	/**
	 * Alias mapper
	 */
	private AliasMapper aliasMapper = null;

	/**
	 * <pre> Returns the object aliased for the xml. Al Class that is to retrived must implement
	 * {@link XMLStreamProvider} in order to parse xml into Object.</pre>
	 * 
	 * <h5>Implementation Sample</h5>
	 * <pre>
	 * <h6>Calling method</h6>
	 * <code>
	 * 	public static void main(String[] args) {
	 * 
	 * 		String testXml = "<sample>" +
	 * 		"<testName>Test Name</testName>" +
	 * 		"<testAddr>TVM</testAddr>"+
	 * 		"<childSample>" +
	 * 		"<childName>Child Name 1.1</childName>"+
	 * 		"<childName2>Child Name 1.2</childName2>"+
	 * 		"<childs>" +
	 * 		"<child>child 1</child>"+
	 * 		"<child>child 2</child>"+
	 * 		"</childs>"+
	 * 		"</childSample>"+
	 * 		"<childSample>" +
	 * 		"<childName>Child Name 2.1</childName>"+
	 * 		"<childName2>Child Name 2.2</childName2>"+
	 * 		"<childs>" +
	 * 		"<child>child 3</child>"+
	 * 		"<child>child 4</child>"+
	 * 		"</childs>"+
	 * 		"</childSample>"+
	 * 		"</sample>";
	 * 
	 * 		XMLStream xmlSteam = new XMLStream();
	 * 		xmlSteam.alias("sample", Sample.class);
	 * 		xmlSteam.alias("childSample", ChildSample.class);
	 * 		xmlSteam.alias("childs", Childs.class);
	 * 		xmlSteam.aliasField("childName", ChildSample.class, "childName1");
	 * 		xmlSteam.aliasField("child", Childs.class, "childs");
	 * 		xmlSteam.aliasField("child", Childs.class,String.class.getName());
	 * 		try {
	 * 			Sample sample = (Sample)xmlSteam.fromXML(testXml);
	 * 			System.out.println("sample.getTestName() : "+sample.getTestName());
	 * 			System.out.println("sample.getAddressName() : "+sample.getAddressName());
	 * 			System.out.println("sample.getChildSample().size : "+sample.getChildSample().size());
	 * 
	 * 			Enumeration enumeration = sample.getChildSample().elements();
	 * 			while (enumeration.hasMoreElements()) {
	 * 			ChildSample element = (ChildSample) enumeration.nextElement();
	 * 			System.out.println("Child  Name 1"+element.getChildName1());
	 * 			System.out.println("Child  Name 2"+element.getChildName2());
	 * 		}
	 * 		System.out.println("XML Obtained ........\n"+xmlSteam.toXml(sample));
	 * 		} catch (CannotResolveClassException e) {
	 * 			// TODO Auto-generated catch block
	 * 			e.printStackTrace();
	 * 		} catch (SecurityException e) {
	 * 			// TODO Auto-generated catch block
	 * 			e.printStackTrace();
	 * 		}
	 * }
	 *  </code>
	 * <h6>ChildSample class</h6>
	 * <code>
	 *  public class ChildSample implements XMLStreamProvider{
	 *  
	 *  private String childName1 = null;
	 *  
	 *  private String childName2 = null;
	 *  
	 *  private Childs childs = null;
	 *  
	 *  public ChildSample() {
	 *  }
	 *  
	 *  public String getChildName1() {
	 *  	return childName1;
	 *  }
	 *
	 *  public Object getFieldValue(String fieldName) {
	 *  	if(fieldName.equalsIgnoreCase("childName1")){
	 *  		return childName1;
	 *  	}
	 *  	if(fieldName.equalsIgnoreCase("childName2")){
	 *  		return childName2;
	 *  	}
	 *  	if(fieldName.equalsIgnoreCase("childs")){
	 *  		return childs;
	 *  	}
	 *  return null;
	 *  }
	 *  
	 *  public void setFieldValue(String fieldName, Object newValue) {
	 *  	if(fieldName.equalsIgnoreCase("childName1")){
	 *  		childName1=(String) newValue;
	 *  	}
	 *  	if(fieldName.equalsIgnoreCase("childName2")){
	 *  		childName2=(String) newValue;
	 *  	}
	 *  	if(fieldName.equalsIgnoreCase("childs")){
	 *  		childs=(Childs) newValue;
	 *  	}
	 *  }
	 *  
	 *  public String getChildName2() {
	 *  	return childName2;
	 *  }
	 *
	 *  public Vector getDeclaredFields() {
	 *  	Vector declaredFields = new Vector();
	 *  	declaredFields.addElement(new Field("childName1"));
	 *  	declaredFields.addElement(new Field("childName2"));
	 *  	declaredFields.addElement(new Field("childs"));
	 *  	return declaredFields;
	 *  }
	 *  }
	 *  </code>
	 *  <h6>Childs class</h6>
	 *  <code>
	 *  public class Childs implements XMLStreamProvider{
	 *  
	 *  private Vector childs = new Vector();
	 *  
	 *  public Childs() {
	 *  }
	 *
	 *  public Vector getDeclaredFields() {
	 *  	Vector fields = new Vector();
	 *  	fields.addElement(new Field("childs"));
	 *  	return fields;
	 *  }
	 *  
	 *  public Object getFieldValue(String fieldName) {
	 *  	if(fieldName.equalsIgnoreCase("childs"))
	 *  		return childs;
	 *  	return null;
	 *  }
	 *  
	 *  public void setFieldValue(String fieldName, Object newValue) {
	 *  	if(fieldName.equalsIgnoreCase("childs")|| fieldName.equalsIgnoreCase(String.class.getName()))
	 *  		childs.addElement((String) newValue);
	 *  }
	 *  }
	 * </code>
	 * </pre>
	 * 
	 * @param xml <tt> The XML doc to be converted</tt>
	 * @return <tt> The Obeject representing the XML doc , null in case of failure.</tt> 
	 * @throws CannotResolveClassException
	 */
	public Object fromXML(String xml) throws CannotResolveClassException{
		if(xml ==null || xml.equals("")) return null;
		kXMLElement xmlRoot = new kXMLElement();
		xmlRoot.parseString(xml);
		Class aliasedClass = (Class) aliasMapper.getAliases().get(xmlRoot.getTagName());
		if(aliasedClass == null) throw new CannotResolveClassException(xmlRoot.getTagName()+" : "+xmlRoot.getLineNr());
		//Creating referenced class instance
		try {
			return parseLocalFromXML(xmlRoot, (XMLStreamProvider) aliasedClass.newInstance());
		} catch (ClassCastException e) {
			// TODO Auto-generated catch block
			//e.printStackTrace();
			throw new CannotResolveClassException("The aliased class ["+aliasedClass.getName()+"] type must implement com.aosp.xmlparser.XMLStreamProvider.");
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * <pre> Returns the XML doc for the specified xmlObj. Al Classes referenced must implement
	 * {@link XMLStreamProvider} in order to parse Object into xml.</pre>
	 * 
	 * <h5>Implementation Sample</h5>
	 * <pre>
	 * <h6>Calling method</h6>
	 * <code>
	 * 	public static void main(String[] args) {
	 * 
	 * 		String testXml = "<sample>" +
	 * 		"<testName>Test Name</testName>" +
	 * 		"<testAddr>TVM</testAddr>"+
	 * 		"<childSample>" +
	 * 		"<childName>Child Name 1.1</childName>"+
	 * 		"<childName2>Child Name 1.2</childName2>"+
	 * 		"<childs>" +
	 * 		"<child>child 1</child>"+
	 * 		"<child>child 2</child>"+
	 * 		"</childs>"+
	 * 		"</childSample>"+
	 * 		"<childSample>" +
	 * 		"<childName>Child Name 2.1</childName>"+
	 * 		"<childName2>Child Name 2.2</childName2>"+
	 * 		"<childs>" +
	 * 		"<child>child 3</child>"+
	 * 		"<child>child 4</child>"+
	 * 		"</childs>"+
	 * 		"</childSample>"+
	 * 		"</sample>";
	 * 
	 * 		XMLStream xmlSteam = new XMLStream();
	 * 		xmlSteam.alias("sample", Sample.class);
	 * 		xmlSteam.alias("childSample", ChildSample.class);
	 * 		xmlSteam.alias("childs", Childs.class);
	 * 		xmlSteam.aliasField("childName", ChildSample.class, "childName1");
	 * 		xmlSteam.aliasField("child", Childs.class, "childs");
	 * 		xmlSteam.aliasField("child", Childs.class,String.class.getName());
	 * 		try {
	 * 			Sample sample = (Sample)xmlSteam.fromXML(testXml);
	 * 			System.out.println("sample.getTestName() : "+sample.getTestName());
	 * 			System.out.println("sample.getAddressName() : "+sample.getAddressName());
	 * 			System.out.println("sample.getChildSample().size : "+sample.getChildSample().size());
	 * 
	 * 			Enumeration enumeration = sample.getChildSample().elements();
	 * 			while (enumeration.hasMoreElements()) {
	 * 			ChildSample element = (ChildSample) enumeration.nextElement();
	 * 			System.out.println("Child  Name 1"+element.getChildName1());
	 * 			System.out.println("Child  Name 2"+element.getChildName2());
	 * 		}
	 * 		System.out.println("XML Obtained ........\n"+xmlSteam.toXml(sample));
	 * 		} catch (CannotResolveClassException e) {
	 * 			// TODO Auto-generated catch block
	 * 			e.printStackTrace();
	 * 		} catch (SecurityException e) {
	 * 			// TODO Auto-generated catch block
	 * 			e.printStackTrace();
	 * 		}
	 * }
	 *  </code>
	 * <h6>ChildSample class</h6>
	 * <code>
	 *  public class ChildSample implements XMLStreamProvider{
	 *  
	 *  private String childName1 = null;
	 *  
	 *  private String childName2 = null;
	 *  
	 *  private Childs childs = null;
	 *  
	 *  public ChildSample() {
	 *  }
	 *  
	 *  public String getChildName1() {
	 *  	return childName1;
	 *  }
	 *
	 *  public Object getFieldValue(String fieldName) {
	 *  	if(fieldName.equalsIgnoreCase("childName1")){
	 *  		return childName1;
	 *  	}
	 *  	if(fieldName.equalsIgnoreCase("childName2")){
	 *  		return childName2;
	 *  	}
	 *  	if(fieldName.equalsIgnoreCase("childs")){
	 *  		return childs;
	 *  	}
	 *  return null;
	 *  }
	 *  
	 *  public void setFieldValue(String fieldName, Object newValue) {
	 *  	if(fieldName.equalsIgnoreCase("childName1")){
	 *  		childName1=(String) newValue;
	 *  	}
	 *  	if(fieldName.equalsIgnoreCase("childName2")){
	 *  		childName2=(String) newValue;
	 *  	}
	 *  	if(fieldName.equalsIgnoreCase("childs")){
	 *  		childs=(Childs) newValue;
	 *  	}
	 *  }
	 *  
	 *  public String getChildName2() {
	 *  	return childName2;
	 *  }
	 *
	 *  public Vector getDeclaredFields() {
	 *  	Vector declaredFields = new Vector();
	 *  	declaredFields.addElement(new Field("childName1"));
	 *  	declaredFields.addElement(new Field("childName2"));
	 *  	declaredFields.addElement(new Field("childs"));
	 *  	return declaredFields;
	 *  }
	 *  }
	 *  </code>
	 *  <h6>Childs class</h6>
	 *  <code>
	 *  public class Childs implements XMLStreamProvider{
	 *  
	 *  private Vector childs = new Vector();
	 *  
	 *  public Childs() {
	 *  }
	 *
	 *  public Vector getDeclaredFields() {
	 *  	Vector fields = new Vector();
	 *  	fields.addElement(new Field("childs"));
	 *  	return fields;
	 *  }
	 *  
	 *  public Object getFieldValue(String fieldName) {
	 *  	if(fieldName.equalsIgnoreCase("childs"))
	 *  		return childs;
	 *  	return null;
	 *  }
	 *  
	 *  public void setFieldValue(String fieldName, Object newValue) {
	 *  	if(fieldName.equalsIgnoreCase("childs") || fieldName.equalsIgnoreCase(String.class.getName()))
	 *  		childs.addElement((String) newValue);
	 *  }
	 *  }
	 * </code>
	 * </pre>
	 * 
	 * @param xmlObj <tt> {@link XMLStreamProvider} instance to be converted. </tt>
	 * @return <tt>The XML doc representing the xmlObj specified.</tt>
	 */
	public String toXml(XMLStreamProvider xmlObj){
		if(xmlObj == null) return null;
		kXMLElement xmlRoot = new kXMLElement();
		String alias = (String) aliasMapper.getTypeToAlias().get(xmlObj.getClass());
		if(alias == null) alias = xmlObj.getClass().getName();
		xmlRoot.setTagName(alias);
		return parseLocalToXML(xmlRoot, xmlObj,null);
	}


	/**
	 * recursive parsing method
	 * @param xmlElement
	 * @param xmlObj 
	 * @param parentObj <tt> Used in case of a Vector that containing String.</tt>
	 * @return
	 */
	private String parseLocalToXML(kXMLElement xmlElement,Object xmlObj,Object parentObj){
		//logger.debug("parseLocaltoXml(kXMLElement xmlElement,Object xmlObj) called for xmlObj = "+xmlObj.getClass().getName());
		if(xmlObj instanceof String){
			//logger.debug("String content found : "+(String) xmlObj);
			xmlElement.setContent((String) xmlObj);			
		}else if(xmlObj instanceof XMLStreamProvider){
			Vector fields = ((XMLStreamProvider)xmlObj).getDeclaredFields();
			Enumeration enumeration = fields.elements();
			while (enumeration.hasMoreElements()) {
				Field field = (Field) enumeration.nextElement();
				//logger.debug("field name : "+field.getFieldName());
				if(((XMLStreamProvider)xmlObj).getFieldValue(field.getFieldName()) instanceof Vector){
					parseLocalToXML(xmlElement, ((XMLStreamProvider)xmlObj).getFieldValue(field.getFieldName()),xmlObj);
				}else{
					kXMLElement xmlChild = new kXMLElement();
					String aliasForField = (String) aliasMapper.getAliasForField(xmlObj.getClass(),field.getFieldName());
					if(aliasForField == null) aliasForField = field.getFieldName();
					xmlChild.setTagName(aliasForField);
					parseLocalToXML(xmlChild, ((XMLStreamProvider)xmlObj).getFieldValue(field.getFieldName()),null);
					xmlElement.addChild(xmlChild);
				}
			}
		}else if(xmlObj instanceof Vector){
			//logger.debug("Parsing Vecor Object within the "+parentObj.getClass().getName());
			Enumeration enumeration =((Vector) xmlObj).elements();
			while (enumeration.hasMoreElements()) {
				Object vectorObj =  enumeration.nextElement();
				//logger.debug("Type within the vector obj : "+vectorObj.getClass().getName());
				kXMLElement xmlChild = new kXMLElement();
				String alias = null;
				if(vectorObj instanceof String){
					alias = (String) aliasMapper.getAliasForField(parentObj.getClass(),vectorObj.getClass().getName());
				}else{
					alias = (String) aliasMapper.getTypeToAlias().get(vectorObj.getClass());
				}
				if(alias == null) alias = vectorObj.getClass().getName();
				//logger.debug("element name prepared : "+alias);
				xmlChild.setTagName(alias);
				parseLocalToXML(xmlChild,vectorObj,null);
				xmlElement.addChild(xmlChild);
			}
		}
		return xmlElement.toString();
	}

	/**
	 * recursive parsing method
	 * @param xmlElement
	 * @param aliasInstance
	 * @return
	 * @throws CannotResolveClassException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 */
	private Object parseLocalFromXML(kXMLElement xmlElement,XMLStreamProvider aliasInstance) throws CannotResolveClassException, InstantiationException, IllegalAccessException{
		//logger.debug("parseLocal(XMLElement xmlElement,Object aliasInstance) called for "+xmlElement.getTagName()+" and object "+aliasInstance.getClass().getName());
		Enumeration xmlElementEnumerator = xmlElement.enumerateChildren();
		while (xmlElementEnumerator.hasMoreElements()) {
			kXMLElement child = (kXMLElement)(xmlElementEnumerator.nextElement());	
			//logger.debug("Parsing XML tag "+child.getTagName());
			if(child.countChildren()>0){//A class expected
				Class aliasedClass = (Class) aliasMapper.getAliases().get(child.getTagName());	
				if(aliasedClass == null) {
					throw new CannotResolveClassException(child.getTagName()+" : "+child.getLineNr());
				}
				try{
					XMLStreamProvider aliasedInstance =(XMLStreamProvider)aliasedClass.newInstance();	
					//logger.debug("Tag Type Obtained : "+aliasedInstance.getClass().getName());

					if(aliasMapper.getFieldAliases(aliasedInstance.getClass(),child.getTagName())!=null){
						aliasInstance.setFieldValue(aliasMapper.getFieldAliases(aliasedInstance.getClass(),child.getTagName()),
								parseLocalFromXML(child, aliasedInstance));
					}else{
						aliasInstance.setFieldValue(child.getTagName(), parseLocalFromXML(child, aliasedInstance));
					}
				}catch (ClassCastException e) {
					// TODO Auto-generated catch block
					//e.printStackTrace();
					throw new CannotResolveClassException("The aliased class ["+aliasedClass.getName()+"] type must implement com.aosp.xmlparser.XMLStreamProvider.");
				}
			}else{//String expected
				//logger.debug("Last Element "+child.getTagName());
				if(aliasMapper.getFieldAliases(aliasInstance.getClass(),child.getTagName())!=null){
					//System.out.println(" aliasMapper Field Alias "+aliasMapper.getFieldAliases(aliasInstance.getClass(),child.getTagName()));
					aliasInstance.setFieldValue(aliasMapper.getFieldAliases(aliasInstance.getClass(),child.getTagName()),child.getContents());
				}else{
					aliasInstance.setFieldValue(child.getTagName(), child.getContents());
				}
			}
		}
		return aliasInstance;
	}


	/**
	 * <pre> Aliases for Class </pre>
	 * @param fieldName <tt> alias appeared in the xml</tt>
	 * @param type <tt> Class type</tt>
	 */
	public void alias(String fieldName,Class type){
		aliasMapper.alias(fieldName, type);
	}

	/**
	 * <pre> Aliases for a field in the class.</pre>
	 * @param alias <tt> alias appeared in the xml</tt>
	 * @param definedIn <tt> Class type where the field is defined </tt>
	 * @param fieldName <tt> field name within the class </tt>
	 */
	public void aliasField(String alias, Class definedIn, String fieldName){
		aliasMapper.aliasField(alias, definedIn, fieldName);
	}

	/**
	 * 
	 */
	public XMLStream() {
		this.aliasMapper = new AliasMapper();
		//logger.setLevel(Level.DEBUG);
	}


}
